/*****************************************************************************
 *  This file is part of the OpenDomo project.
 *  Copyright(C) 2011 OpenDomo Services SL
 *  
 *  Daniel Lerch Hostalot <dlerch@opendomo.com>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *****************************************************************************/

// ----------------------------------------------------------------------------
// author: Daniel Lerch <dlerch@gmail.com>, http://opendomo.com
// date: January 2008
//
// CHANGES:
//
// ----------------------------------------------------------------------------

#include <stdio.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <strings.h>
#include <stdlib.h>
#include <signal.h>
#include <stdlib.h>
#include <upnplog.h>
#include <upnp/ithread.h>
#include <upnp/upnp.h>
#include <upnp/upnptools.h>
#include <util.h>


#define UPNPCPD_HELLO         "OpenDomo upnpcpd v0.1"
#define UPNPCPD_PORT          9999
#define UPNPCPD_CMD_GETVAR    "GETVAR"
#define UPNPCPD_CMD_ACTION    "ACTION"
#define UPNPCPD_ERR_GETVAR    "GETVAR UPnP error"
#define UPNPCPD_ERR_ACTION    "ACTION UPnP error"
#define UPNPCPD_ERR_CMD       "Invalid command error"

#define BUFLEN 512


UpnpClient_Handle hdl = -1;

typedef struct
{
   int pipe;
} 
user_data_t;



// {{{ upnpcpd_callback()
int upnpcpd_callback(Upnp_EventType event_type, void *event, void *cookie)
{
   static int pipe = -1;

   if(cookie)
   {
      user_data_t *usrdata = (user_data_t*) cookie;
      pipe = usrdata->pipe;       
   }

   switch(event_type) 
   {
      case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE:
      {
         //printf("UPNP_DISCOVERY_ADVERTISEMENT_ALIVE\n");
         break;
      }
      case UPNP_DISCOVERY_SEARCH_RESULT:
      {
         //struct Upnp_Discovery *dsc = (struct Upnp_Discovery *)event;
         //printf("%s : ", dsc->DeviceId);
         //printf("%s\n", dsc->Location);

         /*
         if(pipe!=-1)
         {
            char msg[256];
            snprintf(msg, sizeof(msg), "UPNP_DISCOVERY_SEARCH_RESULT : %s : "
            "%s", dsc->DeviceId, dsc->Location);
            write(pipe, msg, strlen(msg));
         }
         */
      }

      case UPNP_DISCOVERY_SEARCH_TIMEOUT:
      {
         //printf("UPNP_DISCOVERY_SEARCH_TIMEOUT\n");
         break;
      }

      case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE:
      {
         //printf("UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE\n");
         struct Upnp_Discovery *dsc = (struct Upnp_Discovery *)event;
         printf("DeviceId: %s bye\n", dsc->DeviceId);
         break;
      }

      case UPNP_CONTROL_ACTION_REQUEST:
      {
         //printf("UPNP_CONTROL_ACTION_REQUEST\n");
         break;
      }

      case UPNP_CONTROL_ACTION_COMPLETE:
      {
         //printf("UPNP_CONTROL_ACTION_COMPLETE\n");
         /*      
         struct Upnp_Action_Complete *action = 
            (struct Upnp_Action_Complete*) event;

         char *value = util_get_first_document_item(action->ActionResult, 
               (char*)cookie);

         if(value)
         {   
           free(value);
         }
         */

         if(pipe!=-1)
         {
            char *msg = "1\n";
            write(pipe, msg, strlen(msg));
         }
        
         break;
      }

      case UPNP_CONTROL_GET_VAR_COMPLETE:
      {
         //printf("UPNP_CONTROL_GET_VAR_COMPLETE\n");
         struct Upnp_State_Var_Complete *sv_event = 
            (struct Upnp_State_Var_Complete * )event;

         if(sv_event->ErrCode != UPNP_E_SUCCESS) 
         {
            upnplog_printf(UPNPLOG_ERROR, 
               "[upnpcpd], upnpcpd_callback() error: %d\n", sv_event->ErrCode);

            if(pipe!=-1)
            {
               char msg[256];
               snprintf(msg, sizeof(msg), "%s : %d\n", 
                  UPNPCPD_ERR_GETVAR, sv_event->ErrCode);
               write(pipe, msg, strlen(msg));
            }
         } 
         else 
         {
            if(pipe!=-1)
            {
               char msg[256];
               snprintf(msg, sizeof(msg), "%s\n", sv_event->CurrentVal);
               write(pipe, msg, strlen(msg));
            }
         }


         break;
      }

      case UPNP_EVENT_RECEIVED:
      {
         //printf("UPNP_EVENT_RECEIVED\n");
         break;
      }

      case UPNP_EVENT_SUBSCRIBE_COMPLETE:
      {
         //printf("UPNP_EVENT_SUBSCRIBE_COMPLETE\n");
         break;
      }

      case UPNP_EVENT_UNSUBSCRIBE_COMPLETE:
      {
         //printf("UPNP_EVENT_UNSUBSCRIBE_COMPLETE\n");
         break;
      }

      case UPNP_EVENT_RENEWAL_COMPLETE:
      {
         //printf("UPNP_EVENT_RENEWAL_COMPLETE\n");
         break;
      }

      case UPNP_EVENT_AUTORENEWAL_FAILED:
      {
         //printf("UPNP_EVENT_AUTORENEWAL_FAILED\n");
         break;
      }

      case UPNP_EVENT_SUBSCRIPTION_EXPIRED:
      {
         //printf("UPNP_EVENT_SUBSCRIPTION_EXPIRED\n");
         break;
      }

      case UPNP_EVENT_SUBSCRIPTION_REQUEST:
      {
         //printf("UPNP_EVENT_SUBSCRIPTION_REQUEST\n");
         break;
      }

      case UPNP_CONTROL_GET_VAR_REQUEST:
      {
         //printf("UPNP_CONTROL_GET_VAR_REQUEST\n");
         break;
      }

      
      break;
   }

   return 0;
}
// }}}

// {{{ upnpcpd_init()
int upnpcpd_init(user_data_t *data)
{
   int ret=0;

   /* Initialize the UPnP Library */
   if( (ret=UpnpInit(NULL, 0)) != UPNP_E_SUCCESS)
   {
      upnplog_printf(UPNPLOG_ERROR, "[upnpcpd], UpnpInit() error: %d\n", ret);
      return ret;
   }

   /* Register as a client */
   if( (ret=UpnpRegisterClient(upnpcpd_callback, data, &hdl)) != UPNP_E_SUCCESS)
   {
      upnplog_printf(UPNPLOG_ERROR, 
         "[upnpcpd], UpnpRegisterClient() error %d\n", ret);
      return ret;
   }

   return 0;
}
// }}}

// {{{ upnpcpd_cleanup()
int upnpcpd_cleanup()
{
   int ret=0;

   /* Initialize the UPnP Library */
   if( (ret=UpnpUnRegisterClient(hdl)) != UPNP_E_SUCCESS)
   {
      upnplog_printf(UPNPLOG_ERROR, 
         "[upnpcpd], UpnpUnRegisterClient() error: %d\n", ret);
      return ret;
   }

   UpnpFinish();

   return 0;
}
// }}}

// {{{ upnpcpd_socket_init()
int upnpcpd_socket_init(int port)
{
   /* Descriptor del socket */
   int socket_descriptor;

   /* Configuracion del socket */
   struct sockaddr_in sin; 

   /* Crea un socket TCP */
   socket_descriptor = socket (AF_INET, SOCK_STREAM, 0);
   if (socket_descriptor == -1) 
   {
      upnplog_printf(UPNPLOG_ERROR, "[upnpcpd] socket(): %s\n", 
         strerror(errno));
      return -1;
   }

   /* Configura el socket */
   memset(&sin, 0, sizeof(sin));
   sin.sin_family =  AF_INET;
   sin.sin_addr.s_addr = INADDR_ANY;
   sin.sin_port = htons (port);

   /* Une el socket al puerto X */
   if (bind(socket_descriptor,(struct sockaddr *)&sin,sizeof(sin)) == -1) 
   {
      upnplog_printf(UPNPLOG_ERROR, "[upnpcpd] bind(): %s\n", strerror(errno));
      return -1;
   }

   /* Configura la cola del socket */
   if (listen(socket_descriptor, 20) == -1) 
   {
      upnplog_printf(UPNPLOG_ERROR, "[upnpcpd] listen(): %s\n", 
         strerror(errno));
      return -1;
   }

   return socket_descriptor;
}
// }}}

// {{{ upnpcpd_socket_accept() 
int upnpcpd_socket_accept(int sockd)
{
   /* Para obtener la configuracion del socket */
   struct sockaddr addr;
   size_t address_size = sizeof(addr);

   /* Descriptor del socket temporal */
   int temp_socket_descriptor;

   /* Aceptamos la conexion */
   if((temp_socket_descriptor =  
      accept(sockd, &addr, &address_size))==-1)
   {
      upnplog_printf(UPNPLOG_ERROR, "[upnpcpd] accept(): %s\n", 
         strerror(errno));
      return -1;
   }

   return temp_socket_descriptor;
}
// }}}

// {{{ upnpcpd_pipe_read()
int upnpcpd_pipe_read(int fd, char *buffer, size_t len, int timeout)
{
   struct timeval tv;
   fd_set rfds;
   int n;
   int ret;
   buffer[0]=0;

   FD_ZERO(&rfds);
   FD_SET(fd, &rfds);
   tv.tv_sec = timeout;
   tv.tv_usec = 0;

   if((ret = select(fd+1, &rfds, NULL, NULL, &tv))==-1)
   { 
      upnplog_printf(UPNPLOG_ERROR, 
         "[upnpcpd] upnpcpd_socket_read(), select(): %s\n",  strerror(errno));   
      return -1;
   }

   if(ret==0)
      return -1;

   if((n = read(fd, buffer, len))<0)
      upnplog_printf(UPNPLOG_ERROR,"[upnpcpd] upnpcpd_socket_read(), recv()\n");   
   buffer[n]=0;

   if(n==0)
      return -1;

   return 0;
}

// }}}

// {{{ upnpcpd_socket_read()
int upnpcpd_socket_read(int fd, char *buffer, size_t len, int timeout)
{
   struct timeval tv;
   fd_set rfds;
   int n;
   int ret;
   buffer[0]=0;

   FD_ZERO(&rfds);
   FD_SET(fd, &rfds);
   tv.tv_sec = timeout;
   tv.tv_usec = 0;

   if((ret = select(fd+1, &rfds, NULL, NULL, &tv))==-1)
   { 
      upnplog_printf(UPNPLOG_ERROR, 
         "[upnpcpd] upnpcpd_socket_read(), select(): %s\n",  strerror(errno));   
      return -1;
   }

   if(ret==0)
      return -1;


   if((n=recv(fd, buffer, len, 0))<0) 
      upnplog_printf(UPNPLOG_ERROR,"[upnpcpd] upnpcpd_socket_read(), recv()\n");   
   buffer[n]=0;

   if(n==0)
      return -1;

   return 0;
}
// }}}

// {{{ upnpcpd_clear_input()
void  upnpcpd_clear_input(char *input)
{
   int i;
   for(i=0; i<strlen(input); i++)
   {
      switch(input[i])
      {
         case '\\':  input[i]=' ';  break;
         case '%':   input[i]=' ';  break;
         case '\n':   input[i]=' ';  break;
         case '\r':   input[i]=' ';  break;
      }
   }
}
// }}}

// {{{ upnpcpd_str_split(()
void upnpcpd_str_split(const char *data, size_t pos, char *res, size_t res_len)
{
   int i=0;
   char *ptr = NULL;
   char tmpdata[strlen(data)+1];
   res[0]=0;

   strncpy(tmpdata, data, sizeof(tmpdata));
   ptr = tmpdata;
   for(i=0; i<=pos; i++)
   {
      if(i==pos)
      {
         strncpy(res, ptr, res_len);
            res[res_len-1]=0;

         if((ptr=strstr(res, " "))!=NULL)
            *ptr=0;

         return;
      }

      if((ptr=strstr(ptr+1, " "))==NULL)
      {
         res[0]=0;
         return;
      }

      while(*ptr==' ')
         ptr++;
   }
}
// }}}

// {{{ upnpcpd_exec_action()
int upnpcpd_exec_action(const char *action_name, const char *csv_params, 
                        const char *action_url, const char *service_type, 
                        user_data_t *usrdata)
{

   IXML_Document *action = NULL;
   int ret=0;
   char *p;
   char *q;
   char *tokptr1;
   char *tokptr2;

   /* action xml */
   for(q=(char*)csv_params;  (p=strtok_r(q, ",", &tokptr1)) != NULL; q = NULL)
   {
      char *param_name = strtok_r(p, "=", &tokptr2);
      char *param_value = strtok_r(NULL, "=", &tokptr2);

      if(!param_name || !param_value)
      {
         upnplog_printf(UPNPLOG_ERROR, 
            "[upnpcpd], upnpcpd_exec_action() param error\n");
         return -1;
      }

      if( (ret=UpnpAddToAction(&action, action_name, service_type, 
               param_name, param_value)) != UPNP_E_SUCCESS)
      {
         upnplog_printf(UPNPLOG_ERROR, 
            "[upnpcpd], UpnpAddToAction() error %d\n", ret);
         return -1;
      }
   }

   /* Send action */
   if( (ret=UpnpSendActionAsync(hdl, action_url, service_type,
         NULL, action, upnpcpd_callback, usrdata)) != UPNP_E_SUCCESS)
   {
      upnplog_printf(UPNPLOG_ERROR, 
         "[upnpcp], UpnpSendActionAsync() error %d\n", ret);
      return -1;
   }

   if(action)
      ixmlDocument_free(action);


   return 0;
}
// }}}



int main(int argc, char **argv)
{
   user_data_t usrdata;
   int fd1[2];
   int fd2[2];
   pid_t pid;  
   char data[BUFLEN];
   char sockbuf[BUFLEN];
   int ret=0;

   if(pipe(fd1)<0 || pipe(fd2)<0)
   {
      perror("pipe()");
      return -1;
   }

   if((pid=fork())<0)
   {
      perror("fork()");
      return -1;
   }

   /* ----------------------------------------------------------------------
      Parent: Listen UPnP messages 
    ------------------------------------------------------------------------ */
   else if(pid>0)
   {
      upnplog_printf(UPNPLOG_INFO, "[upnpcpd] parent start\n");
      close(fd1[0]);
      close(fd2[1]);

      usrdata.pipe = fd1[1];

      
      if(upnpcpd_init(&usrdata) != 0)
      {
         upnplog_printf(UPNPLOG_ERROR, "[upnpcpd], upnpcpd_init()\n");
         return -1;
      }
      

      while(1)
      {
         if(upnpcpd_pipe_read(fd2[0], data, sizeof(data), 60)==-1)
            continue;
         
         char command[16];
         char name[16];
         char url[256];
      
         upnpcpd_str_split(data, 0, command, sizeof(command));

         // Get UPNP Variable
         if(strcmp(command, UPNPCPD_CMD_GETVAR)==0)
         {   
            upnpcpd_str_split(data, 1, name, sizeof(name));
            upnpcpd_str_split(data, 2, url, sizeof(url));

            // Request variable value 
            if( (ret=UpnpGetServiceVarStatusAsync(hdl, url, name, 
                        upnpcpd_callback, &usrdata)) != UPNP_E_SUCCESS)
            {
               upnplog_printf(UPNPLOG_ERROR, 
                  "[upnpcpd], UpnpGetServiceVarStatusAsync() error %d\n", ret);
               write(fd1[1], UPNPCPD_ERR_GETVAR "\n", 
                  strlen(UPNPCPD_ERR_GETVAR)+1);
            }  
         }
         // Execute UPNP Action
         else if(strcmp(command, UPNPCPD_CMD_ACTION)==0)
         {
            char csvparam[256];
            char servtype[256];
            upnpcpd_str_split(data, 1, name, sizeof(name));
            upnpcpd_str_split(data, 2, csvparam, sizeof(csvparam));
            upnpcpd_str_split(data, 3, url, sizeof(url));
            upnpcpd_str_split(data, 4, servtype, sizeof(servtype));

            if(upnpcpd_exec_action(name, csvparam, url, servtype, &usrdata)==-1)
            {
               upnplog_printf(UPNPLOG_ERROR, 
                  "[upnpcpd], upnpcpd_exec_action()\n");
               write(fd1[1], UPNPCPD_ERR_ACTION "\n",  
                  strlen(UPNPCPD_ERR_ACTION)+1);
            }
         }
         else
         {
            upnplog_printf(UPNPLOG_ERROR, "[upnpcpd] invalid command: %s\n", 
               command);   
            write(fd1[1], UPNPCPD_ERR_CMD "\n", strlen(UPNPCPD_ERR_CMD)+1);
         }
      }

      if(upnpcpd_cleanup() != 0)
      {
         upnplog_printf(UPNPLOG_ERROR, "[upnpcpd], upnpcpd_cleanup()\n");
         return -1;
      }


   }

   /* ----------------------------------------------------------------------
      Child: execute user commands 
    ------------------------------------------------------------------------ */
   else
   {
      upnplog_printf(UPNPLOG_INFO, "[upnpcpd] child start\n");
      close(fd1[1]);
      int sockd = upnpcpd_socket_init(UPNPCPD_PORT);
      if(sockd==-1)
      {
         upnplog_printf(UPNPLOG_ERROR, "[upnpcpd] upnpcpd_socket_init()\n");
         exit(0);
      }

      while(1)
      {
         // Accept client connection 
         int tmpsockd = upnpcpd_socket_accept(sockd);
         if(tmpsockd==-1)
         {
            upnplog_printf(UPNPLOG_ERROR, 
               "[upnpcpd] upnpcpd_socket_accept()\n");
            sleep(2);
            continue;
         }
    
         // Send hello message
         if(send(tmpsockd, UPNPCPD_HELLO "\n", strlen(UPNPCPD_HELLO)+1, 0)==-1) 
         { 
            upnplog_printf(UPNPLOG_ERROR, "[upnpcpd] send()\n");   
            close(tmpsockd);
            continue;
         }

         // Read client data
         if(upnpcpd_socket_read(tmpsockd, sockbuf, sizeof(sockbuf), 10)==-1)
         {   
            close(tmpsockd);
            continue;
         }

         // Clear input data 
         upnpcpd_clear_input(sockbuf);

         // Send to parent 
         write(fd2[1], sockbuf, strlen(sockbuf));

         // Read from parent
         if(upnpcpd_pipe_read(fd1[0], data, sizeof(data), 60)==-1)
         {        
            close(tmpsockd);
            continue;
         }

         // Send response to client 
         if(send(tmpsockd, data, strlen(data), 0)==-1) 
         { 
            upnplog_printf(UPNPLOG_ERROR, "[upnpcpd] send()\n");   
            close(tmpsockd);
            continue;
         }

         close(tmpsockd);
         continue;
      }
   }

   return 0;
}


